/*
 * drivers/devfreq/ddrfreq/sunxi_dramfreq.c
 *
 * Copyright(c) 2013-2015 Allwinnertech Co., Ltd.
 *
 * Author: Pan Nan <pannan@allwinnertech.com>
 *
 * SUNXI dram frequency dynamic scaling driver
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 */

#include <asm/tlbflush.h>
#include <linux/clk.h>
#include <linux/cpu.h>
#include <linux/cpufreq.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/devfreq.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
#include <linux/suspend.h>
#include <linux/sunxi_dramfreq.h>
#include <linux/workqueue.h>

enum {
	DEBUG_NONE = 0,
	DEBUG_FREQ = 1,
};
static int debug_mask = DEBUG_FREQ;
module_param(debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP);
#define DRAMFREQ_DBG(mask,format,args...) \
	do { if (mask & debug_mask) printk("[dramfreq] "format,##args); } while (0)
#define DRAMFREQ_ERR(format,args...) \
	printk(KERN_ERR "[dramfreq] ERR:"format,##args)

#define SUNXI_DRAMFREQ_MDFS_RETRIES (3)

static unsigned int sunxi_dramfreq_table[LV_END] = {
	672000, //LV_0
	480000, //LV_1
	336000, //LV_2
	240000, //LV_3
	168000, //LV_4
};

#if defined(CONFIG_ARCH_SUN8IW10)
static int key_masters_int_idx[MASTER_MAX][2] = {
#ifdef CONFIG_EINK_PANEL_USED
	{ MASTER_EINK0, 5 },
	{ MASTER_EDMA,  6 },
	{ MASTER_EINK1, 7 },
#else
	{ MASTER_DE,    9 },
#endif
	{ MASTER_CSI,   3 },
};
#elif defined(CONFIG_ARCH_SUN8IW11)
static int key_masters_int_idx[MASTER_MAX][2] = {
	{ MASTER_GPU,   1 },
	{ MASTER_CSI,   5 },
	{ MASTER_DE,   13 },
};
#elif defined(CONFIG_ARCH_SUN50IW2)
static int key_masters_int_idx[MASTER_MAX][2] = {
	{ MASTER_GPU,   1 },
	{ MASTER_CSI,   5 },
	{ MASTER_DE,   10 },
};
#endif

struct sunxi_dramfreq *dramfreq = NULL;
struct task_struct *sunxi_dramfreq_task = NULL;

#ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC
#define MDFS_LEAST_TIME_RETRIES    (5)
#define MDFS_LEAST_TIME_US         (2000)

struct dramfreq_vb_time_ops {
	int (*get_vb_time) (void);
	int (*get_next_vb_time) (void);
	int (*is_in_vb) (void);
} dramfreq_vbtime_ops;

int dramfreq_set_vb_time_ops(struct dramfreq_vb_time_ops *ops)
{
	dramfreq_vbtime_ops.get_vb_time = ops->get_vb_time;
	dramfreq_vbtime_ops.get_next_vb_time = ops->get_next_vb_time;
	dramfreq_vbtime_ops.is_in_vb = ops->is_in_vb;

	return 0;
}
EXPORT_SYMBOL(dramfreq_set_vb_time_ops);
#endif

#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
static DEFINE_MUTEX(busfreq_lock);

struct busfreq_table {
	char *name;
	struct clk *bus_clk;
	unsigned long normal_freq;
	unsigned long idle_freq;
};

static struct busfreq_table busfreq_tbl[] = {
	{ .name = "ahb1",   .normal_freq = 200000000, .idle_freq =  50000000 },
};

static struct clk *clk_ahb1;

static int sunxi_dramfreq_busfreq_target(const char *name, struct clk *clk,
					unsigned long target_freq)
{
	unsigned long cur_freq;

	mutex_lock(&busfreq_lock);

	if (clk_prepare_enable(clk)) {
		DRAMFREQ_ERR("try to enable %s output failed!\n", name);
		goto err;
	}

	cur_freq = clk_get_rate(clk);
	if (cur_freq == target_freq) {
		mutex_unlock(&busfreq_lock);
		return 0;
	}

	if (clk_set_rate(clk, target_freq)) {
		DRAMFREQ_ERR("try to set %s rate to %lu failed!\n", name, target_freq);
		goto err;
	}

	cur_freq = clk_get_rate(clk);
	if (cur_freq != target_freq) {
		DRAMFREQ_ERR("%s: %lu != %lu\n", name, cur_freq, target_freq);
		goto err;
	}

	mutex_unlock(&busfreq_lock);
	return 0;

err:
	mutex_unlock(&busfreq_lock);
	return -1;
}
#endif /* CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ */

#ifdef CONFIG_SMP
struct cpumask dramfreq_ipi_mask;
static volatile bool cpu_pause[NR_CPUS];
static volatile bool pause_flag;
static bool mdfs_is_paused(void)
{
	smp_rmb();
	return pause_flag;
}
static void mdfs_set_paused(bool pause)
{
	pause_flag = pause;
	smp_wmb();
}
static bool mdfs_cpu_is_paused(unsigned int cpu)
{
	smp_rmb();
	return cpu_pause[cpu];
}
static void mdfs_cpu_set_paused(unsigned int cpu, bool pause)
{
	cpu_pause[cpu] = pause;
	smp_wmb();
}
static void mdfs_cpu_pause(void *info)
{
	unsigned int cpu = raw_smp_processor_id();
	dsb(sy);
	isb();
	mdfs_cpu_set_paused(cpu, true);
	while (mdfs_is_paused());
	mdfs_cpu_set_paused(cpu, false);
}
static void mdfs_cpu_wait(void *info) {}
#endif /* CONFIG_SMP */

#ifdef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
int dramfreq_master_access(enum DRAM_KEY_MASTER master, bool access)
{
	if (dramfreq == NULL)
		return -EINVAL;

	dramfreq->key_masters[master] = access ? 1 : 0;

	if (!dramfreq->pause)
		wake_up_process(sunxi_dramfreq_task);

	return 0;
}
EXPORT_SYMBOL(dramfreq_master_access);
#endif

static int mdfs_cfs(unsigned int freq_jump, struct sunxi_dramfreq *dramfreq,
				unsigned int freq)
{
	volatile unsigned int reg_val;
	unsigned int i, div, n = 4;
	unsigned int rank_num, trefi, trfc, ctrl_freq;
	unsigned int timeout = 1000000000;
	struct dram_para_t *para = &dramfreq->dram_para;

	/* wait for process finished */
	do {
		reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR) & 0x1;
	} while (reg_val && timeout--);

	if (timeout == 0)
		return -EBUSY;

	/*for CFS only support LPDDR */
	ctrl_freq = freq >> 1;
	if ((para->dram_type == 3) || (para->dram_type == 2)) {
		trefi = ((7800*ctrl_freq)/1000 + ((((7800*ctrl_freq)%1000) != 0) ? 1 :0))/32;
		trfc = (350*ctrl_freq)/1000 + ((((350*ctrl_freq)%1000) != 0) ? 1 :0);
	} else {
		trefi = ((3900*ctrl_freq)/1000 + ((((3900*ctrl_freq)%1000) != 0) ? 1 :0))/32;
		trfc = (210*ctrl_freq)/1000 + ((((210*ctrl_freq)%1000) != 0) ? 1 :0);
	}

	/* set dual buffer for timing change and power save */
	reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR);
	reg_val |=(0x1U<<15);
	writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR);

	/* change refresh timing */
	reg_val = readl(dramfreq->dramctl_base + RFSHTMG);
	reg_val &= ~((0xfff<<0)|(0xfff<<16));
	reg_val |= ((trfc<<0)|(trefi<<16));
	writel(reg_val, dramfreq->dramctl_base + RFSHTMG);

	/* change ODT status for power save  */
	if (!((para->dram_tpr13>>12) & 0x1)) {
		if (freq > 400) {
			if ((para->dram_odt_en & 0x1)) {
				for (i = 0; i < n; i++) {
					//byte 0/byte 1
					reg_val = readl(dramfreq->dramctl_base + DXnGCR0(i));
					reg_val &= ~(0x3U<<4);
					reg_val |= (0x0<<4);//ODT dynamic
					writel(reg_val, dramfreq->dramctl_base + DXnGCR0(i));
				}

				rank_num = readl(dramfreq->dramcom_base + MC_WORK_MODE) & 0x1;
				if (rank_num)
					writel(0x00000303, dramfreq->dramctl_base + ODTMAP);
				else
					writel(0x00000201, dramfreq->dramctl_base + ODTMAP);
			}
		} else {
			if ((para->dram_odt_en & 0x1)) {
				for (i = 0; i < n; i++) {
					//byte 0/byte 1
					reg_val = readl(dramfreq->dramctl_base + DXnGCR0(i));
					reg_val &= ~(0x3U<<4);
					reg_val |= (0x2<<4);
					writel(reg_val, dramfreq->dramctl_base + DXnGCR0(i));
				}
				writel(0x0, dramfreq->dramctl_base + ODTMAP);
			}
		}
	}

	/* change pll-ddr N value */
	div = freq / 12;
	reg_val = readl(dramfreq->ccu_base + CCM_PLL_DDR1_REG);
	reg_val &= ~(0x7f<<8);
	reg_val |=(((div-1)<<8));
	writel(reg_val, dramfreq->ccu_base + CCM_PLL_DDR1_REG);

	/* setting MDFS configuration */
	reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR);
	reg_val &= ~(0x1<<2);
	reg_val |= ((freq_jump & 0x1) << 2);    //1: increase  0:decrease
	reg_val |= (0x1<<1);  //CFS mode
	reg_val |= (0x1<<11); //vtf enable
	reg_val |= (0x1<<4);  //bypass
	writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR);

	reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR);
	reg_val |= (0x1U<<0);
	writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR);

	return 0;
}

static int mdfs_dfs(unsigned int freq_jump, struct sunxi_dramfreq *dramfreq,
				unsigned int freq)
{
	volatile unsigned int reg_val;
	unsigned int rank_num, trefi, trfc, ctrl_freq;
	unsigned int i, n = 4;
	unsigned int div, source;
	unsigned int vtf_status;
	unsigned int timeout = 1000000;
	struct dram_para_t *para = &dramfreq->dram_para;

	/* calculate source and divider */
	if (para->dram_tpr9 != 0) {
		if (((para->dram_clk % freq) == 0 ) && ((para->dram_tpr9 % freq) == 0)) {
			if ((para->dram_clk / freq) > (para->dram_tpr9 / freq)) {
				source = !((para->dram_tpr13 >> 6) & 0x1);
				div = para->dram_tpr9 / freq ;
			} else {
				source = (para->dram_tpr13 >> 6) & 0x1;
				div = para->dram_clk / freq ;
			}
		} else if ((para->dram_clk % freq) == 0) {
			source = (para->dram_tpr13 >> 6) & 0x1;
			div = para->dram_clk / freq ;
		} else if ((para->dram_tpr9 % freq) == 0) {
			source = !((para->dram_tpr13 >> 6) & 0x1);
			div = para->dram_tpr9 / freq ;
		} else {
			DRAMFREQ_ERR("unsupported freq!\n");
			return 1;
		}
	} else {
#if defined(CONFIG_ARCH_SUN50IW1)
		source = 1;
#elif defined(CONFIG_ARCH_SUN8IW10) || defined(CONFIG_ARCH_SUN8IW11) || \
	defined(CONFIG_ARCH_SUN50IW2)
		source = (para->dram_tpr13 >> 6) & 0x1;
#endif
		div = para->dram_clk / freq ;
	}

	ctrl_freq = freq >> 1;
	if ((para->dram_type == 3) || (para->dram_type == 2)) {
		trefi = ((7800*ctrl_freq)/1000 + ((((7800*ctrl_freq)%1000) != 0) ? 1 :0))/32;
		trfc = (350*ctrl_freq)/1000 + ((((350*ctrl_freq)%1000) != 0) ? 1 :0);
	}else{
		trefi = ((3900*ctrl_freq)/1000 + ((((3900*ctrl_freq)%1000) != 0) ? 1 :0))/32;
		trfc = (210*ctrl_freq)/1000 + ((((210*ctrl_freq)%1000) != 0) ? 1 :0);
	}

	/* turn off vtf */
	reg_val = readl(dramfreq->dramctl_base + VTFCR);
#if defined(CONFIG_ARCH_SUN50IW1)
	vtf_status = (reg_val & (0x1<<8));
	if (vtf_status) {
		reg_val &= ~(0x1<<8);
		writel(reg_val, dramfreq->dramctl_base + VTFCR);
	}
#elif defined(CONFIG_ARCH_SUN8IW10) || defined(CONFIG_ARCH_SUN8IW11) || \
	defined(CONFIG_ARCH_SUN50IW2)
	vtf_status = (reg_val & (0x3<<8));
	if (vtf_status) {
		reg_val &= ~(0x3<<8);
		writel(reg_val, dramfreq->dramctl_base + VTFCR);
	}
#endif

	/* set dual buffer for timing change and power save */
	reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR);
	/* VTC dual buffer can not be used */
	reg_val |=(0x1U << 15);
	writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR);

	/* change refresh timing */
	reg_val = readl(dramfreq->dramctl_base + RFSHTMG);
	reg_val &= ~((0xfff<<0)|(0xfff<<16));
	reg_val |= ((trfc<<0)|(trefi<<16));
	writel(reg_val, dramfreq->dramctl_base + RFSHTMG);

	/* change ODT status for power save */
	if (!((para->dram_tpr13 >> 12) & 0x1)) {
		if (freq > 400) {
			if ((para->dram_odt_en & 0x1)) {
				for (i = 0; i < n; i++) {
					/* byte 0/byte 1 */
					reg_val = readl(dramfreq->dramctl_base + DXnGCR0(i));
					reg_val &= ~(0x3U<<4);
					reg_val |= (0x0<<4);//ODT dynamic
					writel(reg_val, dramfreq->dramctl_base + DXnGCR0(i));
				}

				rank_num = readl(dramfreq->dramcom_base + MC_WORK_MODE) & 0x1;
				if (rank_num)
					writel(0x00000303, dramfreq->dramctl_base + ODTMAP);
				else
					writel(0x00000201, dramfreq->dramctl_base + ODTMAP);
			}
		} else {
			if ((para->dram_odt_en & 0x1)) {
				for (i = 0; i < n; i++) {
					/* byte 0/byte 1*/
					reg_val = readl(dramfreq->dramctl_base + DXnGCR0(i));
					reg_val &= ~(0x3U<<4);
					reg_val |= (0x2<<4);
					writel(reg_val, dramfreq->dramctl_base + DXnGCR0(i));
				}
				writel(0x0, dramfreq->dramctl_base + ODTMAP);
			}
		}
	}

	/* set the DRAM_CFG_REG divider in CCMU */
	reg_val = readl(dramfreq->ccu_base + CCM_DRAM_CFG_REG);
	reg_val &= ~(0xf<<0);
	reg_val |= ((div-1)<<0);
	writel(reg_val, dramfreq->ccu_base + CCM_DRAM_CFG_REG);

	/* set clock source in CCMU */
	reg_val &= ~(0x3<<20);
	reg_val |= (source<<20);
	writel(reg_val, dramfreq->ccu_base + CCM_DRAM_CFG_REG);

	/* set MDFS register */
	reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR);
	reg_val |= (0x1<<4);   //bypass
	reg_val |= (0x1<<13);  //pad hold
	reg_val &= ~(0x1U<<1); //DFS mode
	writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR);

	reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR);
	reg_val |= (0x1U<<0); //start mdfs
	writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR);

	/* wait for process finished */
	while (timeout--) {
		reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR) & 0x1;
		if (!reg_val)
			break;
	}

	if (timeout == 0)
		return -EBUSY;

	/* turn off dual buffer */
	reg_val = readl(dramfreq->dramcom_base + MC_MDFSCR);
	reg_val &= ~(0x1U<<15);
	writel(reg_val, dramfreq->dramcom_base + MC_MDFSCR);

	/* turn on vtf */
	if (vtf_status) {
#if defined(CONFIG_ARCH_SUN50IW1)
		reg_val = readl(dramfreq->dramctl_base + VTFCR);
		vtf_status |= (0x1<<8);
		writel(reg_val, dramfreq->dramctl_base + VTFCR);
#elif defined(CONFIG_ARCH_SUN8IW10) || defined(CONFIG_ARCH_SUN8IW11) || \
	defined(CONFIG_ARCH_SUN50IW2)
		reg_val = readl(dramfreq->dramctl_base + VTFCR);
		vtf_status |= (0x3<<8);
		writel(reg_val, dramfreq->dramctl_base + VTFCR);
#endif
	}

	return 0;
}

static int sunxi_dramfreq_get_cur_freq(struct device *dev, unsigned long *freq)
{
	unsigned long pll_ddr_rate;
	unsigned int dram_div_m;
#ifdef CONFIG_DEBUG_FS
	ktime_t calltime = ktime_get();
#endif

	if ((readl(dramfreq->ccu_base + 0xF4) >> 20) & 0x1)
		pll_ddr_rate = clk_get_rate(dramfreq->clk_pll_ddr1) / 1000;
	else
		pll_ddr_rate = clk_get_rate(dramfreq->clk_pll_ddr0) / 1000;

	dram_div_m = (readl(dramfreq->ccu_base + 0xF4) & 0x3) + 1;
	*freq = pll_ddr_rate / 2 / dram_div_m;

#ifdef CONFIG_DEBUG_FS
	dramfreq->dramfreq_get_us = ktime_to_us(ktime_sub(ktime_get(), calltime));
#endif

	return 0;
}

unsigned long dramfreq_get(void)
{
	unsigned long cur_freq;

	if (dramfreq == NULL)
		return 0;

	if (dramfreq->ccu_base == NULL
		|| dramfreq->clk_pll_ddr0 == NULL
		|| dramfreq->clk_pll_ddr1 == NULL)
		return 0;

	sunxi_dramfreq_get_cur_freq(NULL, &cur_freq);

	return cur_freq;
}
EXPORT_SYMBOL_GPL(dramfreq_get);

static int sunxi_dramfreq_get_max_freq(void)
{
	return sunxi_dramfreq_table[0];
}

static int sunxi_dramfreq_get_min_freq(void)
{
	unsigned int i, min = UINT_MAX;

	for (i = 0; i < LV_END; i++) {
		if (sunxi_dramfreq_table[i] == 0)
			continue;

		if (sunxi_dramfreq_table[i] < min)
			min = sunxi_dramfreq_table[i];
	}

	return min;
}

static int sunxi_dramfreq_get_valid_freq(unsigned long freq)
{
	unsigned int *valid_freq = &sunxi_dramfreq_table[0];
	int i = 0;

	while (*(valid_freq+1) >= freq) {
		i++;
		if (i >= LV_END)
			break;
		valid_freq++;
	}

	return *valid_freq;
}

static int sunxi_dramfreq_set_rate(unsigned int jump, unsigned int freq_target,
				struct sunxi_dramfreq *dramfreq, unsigned int *masters_access)
{
	int i, ret = 0;
	s64 mdfs_time_us = 0;
	ktime_t calltime;
#ifdef CONFIG_SMP
	unsigned int cpu, cur_cpu, timeout = 0;
#endif

#ifdef CONFIG_SMP
	cpumask_clear(&dramfreq_ipi_mask);
	cpumask_copy(&dramfreq_ipi_mask, cpu_online_mask);
	cur_cpu = raw_smp_processor_id();
	cpumask_clear_cpu(cur_cpu, &dramfreq_ipi_mask);

	local_bh_disable();
	mdfs_set_paused(true);

	preempt_disable();
	smp_call_function_many(&dramfreq_ipi_mask, mdfs_cpu_pause, NULL, 0);
	preempt_enable();

	dsb(sy);
	isb();

	for_each_online_cpu(cpu) {
		if (cpu == cur_cpu)
			continue;
		while (!mdfs_cpu_is_paused(cpu) && timeout < 100) {
			udelay(100);
			timeout++;
		}

		if (timeout >= 100) {
			DRAMFREQ_ERR("Pause cpu%d time out!\n", cpu);
			ret = -EAGAIN;
			goto out;
		}
	}
#endif

	if (jump == FREQ_DOWN) {
		for (i = 0; i < MASTER_MAX; i++) {
			if (masters_access[i] < dramfreq->key_masters[i]) {
				ret = -EINVAL;
				goto out;
			}
		}
	}

	local_irq_disable();

	if (dramfreq->mode == DFS_MODE) {
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC
		while(!dramfreq_vbtime_ops.is_in_vb());
#endif
	}

	calltime = ktime_get();
	flush_tlb_all();
	isb();

	if (dramfreq->mode == CFS_MODE)
		ret = mdfs_cfs(jump, dramfreq, freq_target / 1000);
	else if (dramfreq->mode == DFS_MODE)
		ret = mdfs_dfs(jump, dramfreq, freq_target / 1000);

	mdfs_time_us = ktime_to_us(ktime_sub(ktime_get(), calltime));
	local_irq_enable();

#ifdef CONFIG_SMP
	DRAMFREQ_DBG(DEBUG_FREQ, "[cpu%d] elapsed:%lldus\n", cur_cpu, mdfs_time_us);
#else
	DRAMFREQ_DBG(DEBUG_FREQ, "[cpu0] elapsed:%lldus\n", mdfs_time_us);
#endif

#ifdef CONFIG_DEBUG_FS
	dramfreq->dramfreq_set_us = mdfs_time_us;
#endif

out:
#ifdef CONFIG_SMP
	mdfs_set_paused(false);
	local_bh_enable();
	preempt_disable();
	smp_call_function_many(&dramfreq_ipi_mask, mdfs_cpu_wait, NULL, true);
	preempt_enable();
#endif

	return ret;
}

static int sunxi_dramfreq_target(struct device *dev,
								unsigned long *freq, u32 flags)
{
	struct platform_device *pdev = container_of(dev, struct platform_device, dev);
	struct sunxi_dramfreq *dramfreq = platform_get_drvdata(pdev);
	int i, ret = 0, retries = 0, jump = 0;
	unsigned int masters_access[MASTER_MAX];
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC
	int next_vbtime_us = 0, next_vb_retries = 0;
#endif
	unsigned long cur_freq, valid_freq;
#ifdef CONFIG_CPU_FREQ
	struct cpufreq_policy policy;
#endif

	for (i = 0; i < MASTER_MAX; i++)
		masters_access[i] = dramfreq->key_masters[i];

	if ((dramfreq == NULL) || (dramfreq->devfreq == NULL))
		return -EINVAL;

	mutex_lock(&dramfreq->lock);
	get_online_cpus();

	valid_freq = sunxi_dramfreq_get_valid_freq(*freq);
	if (valid_freq == dramfreq->devfreq->previous_freq) {
		if (*freq != valid_freq)
			*freq = valid_freq;
		goto unlock;
	}

	jump = (valid_freq > dramfreq->devfreq->previous_freq) ? FREQ_UP : FREQ_DOWN;

	DRAMFREQ_DBG(DEBUG_FREQ, "%luKHz->%luKHz start\n",
						dramfreq->devfreq->previous_freq, valid_freq);

#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
	if ((dramfreq->devfreq->previous_freq == dramfreq->devfreq->min_freq) &&
			(jump == FREQ_UP)) {
		for (i = ARRAY_SIZE(busfreq_tbl) - 1; i >= 0; i--) {
			sunxi_dramfreq_busfreq_target(busfreq_tbl[i].name,
					busfreq_tbl[i].bus_clk, busfreq_tbl[i].normal_freq);
		}
		DRAMFREQ_DBG(DEBUG_FREQ, "AHB1:%lu\n", clk_get_rate(clk_ahb1) / 1000000);
	}
#endif

	if (dramfreq->mode == DFS_MODE) {
#ifdef CONFIG_DEVFREQ_DRAM_FREQ_IN_VSYNC
		if (!(dramfreq_vbtime_ops.get_next_vb_time
				&& dramfreq_vbtime_ops.is_in_vb)) {
			DRAMFREQ_ERR("dramfreq_vbtime_ops is not initialized!\n");
			ret = -EINVAL;
			goto unlock;
		}

		do {
			next_vbtime_us = dramfreq_vbtime_ops.get_next_vb_time();
			if (next_vbtime_us < MDFS_LEAST_TIME_US) {
				next_vb_retries++;
				msleep(1);
			} else {
				break;
			}
		} while (next_vb_retries < MDFS_LEAST_TIME_RETRIES);

		if (next_vb_retries >= MDFS_LEAST_TIME_RETRIES) {
			DRAMFREQ_ERR("Retrying next vb time failed, next time!\n");
			ret = -EINVAL;
			goto unlock;
		} else {
			usleep_range(next_vbtime_us - MDFS_LEAST_TIME_US,
							next_vbtime_us - MDFS_LEAST_TIME_US);
		}
#endif
	}

#ifdef CONFIG_CPU_FREQ
	if ((!cpufreq_get_policy(&policy, 0)) && (policy.cur < policy.max))
		__cpufreq_driver_target(&policy, policy.max, CPUFREQ_RELATION_H);
#endif

	do {
		ret = sunxi_dramfreq_set_rate(jump, valid_freq, dramfreq, masters_access);
		if (ret == -EAGAIN)
			retries++;
		else
			break;
	} while (retries < SUNXI_DRAMFREQ_MDFS_RETRIES);

	if (retries >= SUNXI_DRAMFREQ_MDFS_RETRIES) {
		DRAMFREQ_ERR("Retrying mdfs failed, next time!\n");
		ret = -EINVAL;
		goto unlock;
	}

	if (ret == -EBUSY) {
		DRAMFREQ_ERR("mdfs timeout\n");
		goto unlock;
	} else if (ret == -EINVAL) {
		DRAMFREQ_DBG(DEBUG_FREQ, "no need for freq down\n");
		goto unlock;
	}

	sunxi_dramfreq_get_cur_freq(dev, &cur_freq);
	if (cur_freq != valid_freq) {
		DRAMFREQ_ERR("current freq is %lu != %lu\n", cur_freq, valid_freq);
		goto unlock;
	}

	if (*freq != cur_freq)
		*freq = cur_freq;

	DRAMFREQ_DBG(DEBUG_FREQ, "%luKHz->%luKHz ok\n",
					dramfreq->devfreq->previous_freq, cur_freq);

#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
	if ((cur_freq == dramfreq->devfreq->min_freq) && (jump == FREQ_DOWN)) {
		for (i = 0; i < ARRAY_SIZE(busfreq_tbl); i++) {
			sunxi_dramfreq_busfreq_target(busfreq_tbl[i].name,
					busfreq_tbl[i].bus_clk, busfreq_tbl[i].idle_freq);
		}
		DRAMFREQ_DBG(DEBUG_FREQ, "AHB1:%lu\n", clk_get_rate(clk_ahb1) / 1000000);
	}
#endif

unlock:
	put_online_cpus();
	mutex_unlock(&dramfreq->lock);

	return ret;
}

#ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER
extern int sunxi_get_sensor_temp(u32 sensor_num, long *temperature);
bool temptrigger_initialized = false;

static int sunxi_get_dev_status(struct device *dev,
				      struct devfreq_dev_status *stat)
{
	int i, ret;
	long temp = 0, max_temp = 0, std_temp = 0;

	for (i = 0; i < 3; i++) {
		ret = sunxi_get_sensor_temp(i, &temp);
		if (ret == 0)
			max_temp = max(max_temp, temp);
		else
			break;
	}

	stat->private_data = temptrigger_initialized ? &max_temp : &std_temp;
	return 0;
}
#endif

static struct devfreq_dev_profile sunxi_dramfreq_profile = {
	.get_cur_freq = sunxi_dramfreq_get_cur_freq,
	.target       = sunxi_dramfreq_target,
	.freq_table   = sunxi_dramfreq_table,
#ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER
	.get_dev_status	= sunxi_get_dev_status,
#endif
	.max_state    = LV_END,
};

extern int update_devfreq(struct devfreq *devfreq);
static int sunxi_dramfreq_task_func(void *data)
{
	struct devfreq *df = (struct devfreq *)data;

	while (1) {
		mutex_lock(&df->lock);
		update_devfreq(df);
		mutex_unlock(&df->lock);

		set_current_state(TASK_INTERRUPTIBLE);
		schedule();
		if (kthread_should_stop())
			break;
		set_current_state(TASK_RUNNING);
	}

	return 0;
}

static void sunxi_dramfreq_masters_state_init(struct sunxi_dramfreq *dramfreq)
{
	int i;

	for (i = 0; i < MASTER_MAX; i++)
		dramfreq->key_masters[i] = (i == MASTER_CSI) ? 0 : 1;
}

#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
static void sunxi_dramfreq_irq_clear_flag(struct sunxi_dramfreq *dramfreq)
{
	writel(0xFFFFFFFF, dramfreq->dramcom_base + MDFS_IRQ_STATUS0);
	writel(0xFFFFFFFF, dramfreq->dramcom_base + MDFS_IRQ_STATUS1);
}

static void sunxi_dramfreq_irq_mask_control(struct sunxi_dramfreq *dramfreq,
					bool valid)
{
	unsigned int i;
	unsigned int access_value, idle_value;

	access_value = readl(dramfreq->dramcom_base + MDFS_IRQ_MASK_STATUS0);
	idle_value   = readl(dramfreq->dramcom_base + MDFS_IRQ_MASK_STATUS1);

	for (i = 0; i < MASTER_MAX; i++) {
		if (valid) {
			access_value |= (0x1 << key_masters_int_idx[i][1]);
			idle_value   |= (0x1 << key_masters_int_idx[i][1]);
		} else {
			access_value &= (~(0x1 << key_masters_int_idx[i][1]));
			idle_value   &= (~(0x1 << key_masters_int_idx[i][1]));
		}
	}
	writel(access_value, dramfreq->dramcom_base + MDFS_IRQ_MASK_STATUS0);
	writel(idle_value,   dramfreq->dramcom_base + MDFS_IRQ_MASK_STATUS1);
}

static void sunxi_dramfreq_irq_enable_control(struct sunxi_dramfreq *dramfreq,
					bool enable)
{
	unsigned int i;
	unsigned int value;

	value = readl(dramfreq->dramcom_base + MDFS_MASTER_ENABLE);

	for (i = 0; i < MASTER_MAX; i++) {
		if (enable)
			value |= (0x1 << key_masters_int_idx[i][1]);
		else
			value &= (~(0x1 << key_masters_int_idx[i][1]));
	}

	writel(value, dramfreq->dramcom_base + MDFS_MASTER_ENABLE);
}

static irqreturn_t sunxi_dramfreq_irq_handler(int irq, void *data)
{
	struct sunxi_dramfreq *dramfreq = (struct sunxi_dramfreq *)data;
	unsigned int i, idx;
	unsigned int irq_access, irq_idle;
	bool handled = false;

	irq_access = readl(dramfreq->dramcom_base + MDFS_IRQ_STATUS0);
	irq_idle   = readl(dramfreq->dramcom_base + MDFS_IRQ_STATUS1);

	for (i = 0; i < MASTER_MAX; i++) {
		idx = key_masters_int_idx[i][1];
		if ((irq_access >> idx) & 0x1) {
			dramfreq->key_masters[i] = 1;
			irq_access |= (0x1 << idx);
			writel(irq_access,
				dramfreq->dramcom_base + MDFS_IRQ_STATUS0);
			handled = true;
		} else if ((irq_idle >> idx) & 0x1) {
			dramfreq->key_masters[i] = 0;
			irq_idle |= (0x1 << idx);
			writel(irq_idle,
				dramfreq->dramcom_base + MDFS_IRQ_STATUS1);
			handled = true;
		}
	}

	if (!handled) {
		DRAMFREQ_ERR("(IRQ) access=0x%x, idle=0x%x\n",
				irq_access, irq_idle);
		return IRQ_NONE;
	}

	if (!dramfreq->pause)
		wake_up_process(sunxi_dramfreq_task);

	return IRQ_HANDLED;
}
#endif /* CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY */

#ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER
struct delayed_work sunxi_temp_work;
struct workqueue_struct *dramfreq_temp_workqueue;

static void sunxi_dramfreq_temp_work_func(struct work_struct *work)
{
	if ((dramfreq == NULL) || (dramfreq->devfreq == NULL)) {
		DRAMFREQ_ERR("%s: para error\n", __func__);
		return;
	}

	mutex_lock(&dramfreq->devfreq->lock);
	update_devfreq(dramfreq->devfreq);
	mutex_unlock(&dramfreq->devfreq->lock);

	queue_delayed_work_on(0, dramfreq_temp_workqueue, &sunxi_temp_work, msecs_to_jiffies(100));
}
#endif

static int sunxi_dramfreq_governor_state_update(char *name, enum GOVERNOR_STATE type)
{
	if (!strcmp(name, "adaptive")) {
		switch (type) {
		case STATE_INIT:
			dramfreq->pause = 1;
			sunxi_dramfreq_task = kthread_create(sunxi_dramfreq_task_func,
												dramfreq->devfreq, "dramfreq_task");
			if (IS_ERR(sunxi_dramfreq_task))
				return PTR_ERR(sunxi_dramfreq_task);

			get_task_struct(sunxi_dramfreq_task);
			wake_up_process(sunxi_dramfreq_task);
			break;
		case STATE_RUNNING:
			dramfreq->pause = 0;
			wake_up_process(sunxi_dramfreq_task);
			/* set master access init state */
			sunxi_dramfreq_masters_state_init(dramfreq);
#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
			/* set irq mask invalid */
			sunxi_dramfreq_irq_mask_control(dramfreq, false);
			/* clear irq flag */
			sunxi_dramfreq_irq_clear_flag(dramfreq);
			/* set irq enable */
			sunxi_dramfreq_irq_enable_control(dramfreq, true);
#endif
			break;
		case STATE_PAUSE:
#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
			/* set irq disable */
			sunxi_dramfreq_irq_enable_control(dramfreq, false);
			/* set irq mask valid */
			sunxi_dramfreq_irq_mask_control(dramfreq, true);
#endif
			dramfreq->pause = 1;
			wake_up_process(sunxi_dramfreq_task);
			break;
		case STATE_EXIT:
#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
			/* set irq disable */
			sunxi_dramfreq_irq_enable_control(dramfreq, false);
			/* set irq mask valid */
			sunxi_dramfreq_irq_mask_control(dramfreq, true);
#endif
			dramfreq->pause = 1;
			mutex_lock(&dramfreq->devfreq->lock);
			update_devfreq(dramfreq->devfreq);
			mutex_unlock(&dramfreq->devfreq->lock);

			kthread_stop(sunxi_dramfreq_task);
			put_task_struct(sunxi_dramfreq_task);
			break;
		}
	}
#ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER
	else if (!strcmp(name, "temptrigger")) {
		switch (type) {
		case STATE_INIT:
			temptrigger_initialized = true;
			dramfreq_temp_workqueue = create_singlethread_workqueue("dramfreq_temp");
			INIT_DELAYED_WORK(&sunxi_temp_work, sunxi_dramfreq_temp_work_func);
			queue_delayed_work_on(0, dramfreq_temp_workqueue, &sunxi_temp_work, msecs_to_jiffies(100));
			break;
		case STATE_RUNNING:
		case STATE_PAUSE:
			break;
		case STATE_EXIT:
			temptrigger_initialized = false;

			mutex_lock(&dramfreq->devfreq->lock);
			update_devfreq(dramfreq->devfreq);
			mutex_unlock(&dramfreq->devfreq->lock);

			cancel_delayed_work_sync(&sunxi_temp_work);
			destroy_workqueue(dramfreq_temp_workqueue);
			break;
		}
	}
#endif

	return 0;
}

static int sunxi_dramfreq_paras_init(struct sunxi_dramfreq *dramfreq)
{
	struct device_node *dram_np;
	int ret = 0;

	dram_np = of_find_node_by_path("/dram");
	if (!dram_np) {
		DRAMFREQ_ERR("Get dram node failed!\n");
		return -ENODEV;
	}

	if (of_property_read_u32(dram_np, "dram_clk",
								&dramfreq->dram_para.dram_clk)) {
		DRAMFREQ_ERR("Get dram_clk failed!\n");
		ret = -ENODEV;
		goto out_put_node;
	}

	if (of_property_read_u32(dram_np, "dram_type",
								&dramfreq->dram_para.dram_type)) {
		DRAMFREQ_ERR("Get dram_type failed!\n");
		ret = -ENODEV;
		goto out_put_node;
	}

	if (of_property_read_u32(dram_np, "dram_odt_en",
								&dramfreq->dram_para.dram_odt_en)) {
		DRAMFREQ_ERR("Get dram_odt_en failed!\n");
		ret = -ENODEV;
		goto out_put_node;
	}

	if (of_property_read_u32(dram_np, "dram_tpr9",
								&dramfreq->dram_para.dram_tpr9)) {
		DRAMFREQ_ERR("Get dram_tpr9 failed!\n");
		ret = -ENODEV;
		goto out_put_node;
	}

	if (of_property_read_u32(dram_np, "dram_tpr13",
								&dramfreq->dram_para.dram_tpr13)) {
		DRAMFREQ_ERR("Get dram_tpr13 failed!\n");
		ret = -ENODEV;
		goto out_put_node;
	}

	if (!((dramfreq->dram_para.dram_tpr13 >> 11) & 0x1)) {
		ret = -EINVAL;
		goto out_put_node;
	}

	dramfreq->mode = (dramfreq->dram_para.dram_tpr13 >> 10) & 0x1;

out_put_node:
	of_node_put(dram_np);
	return ret;
}

static int sunxi_dramfreq_resource_init(struct platform_device *pdev,
					struct sunxi_dramfreq *dramfreq)
{
	int ret = 0;

	dramfreq->dramcom_base = of_iomap(pdev->dev.of_node, 0);
	if (!dramfreq->dramcom_base) {
		DRAMFREQ_ERR("Map dramcom_base failed!\n");
		ret = -EBUSY;
		goto out;
	}

	dramfreq->dramctl_base = of_iomap(pdev->dev.of_node, 1);
	if (!dramfreq->dramctl_base) {
		DRAMFREQ_ERR("Map dramctl_base failed!\n");
		ret = -EBUSY;
		goto out;
	}

	dramfreq->ccu_base = of_iomap(pdev->dev.of_node, 2);
	if (!dramfreq->ccu_base) {
		DRAMFREQ_ERR("Map ccu_base failed!\n");
		ret = -EBUSY;
		goto out;
	}

#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
	dramfreq->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
	if (!dramfreq->irq) {
		DRAMFREQ_ERR("Map IRQ failed!\n");
		ret = -EBUSY;
		goto out;
	}
#endif

	dramfreq->clk_pll_ddr0 = of_clk_get(pdev->dev.of_node, 0);
	if (IS_ERR(dramfreq->clk_pll_ddr0)) {
		DRAMFREQ_ERR("Get clk_pll_ddr0 failed!\n");
		ret = -EINVAL;
		goto out;
	}

	dramfreq->clk_pll_ddr1 = of_clk_get(pdev->dev.of_node, 1);
	if (IS_ERR(dramfreq->clk_pll_ddr1)) {
		DRAMFREQ_ERR("Get clk_pll_ddr1 failed!\n");
		ret = -EINVAL;
		goto out;
	}

#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
	clk_ahb1 = of_clk_get(pdev->dev.of_node, 2);
	if (IS_ERR(clk_ahb1)) {
		DRAMFREQ_ERR("Get clk_ahb1 failed!\n");
		ret = -EINVAL;
		goto out;
	}

	busfreq_tbl[0].bus_clk = clk_ahb1;
#endif

out:
	return ret;
}

static void __array_insert_sort(unsigned int *array, int count)
{
	unsigned int i, j, temp;

	for (i = 1; i < count; i++) {
		temp = array[i];
		j = i-1;

		while (array[j] < temp && j >= 0) {
			array[j+1] = array[j];
			j--;
		}

		if (j != (i-1))
			array[j+1] = temp;
	}
}

static void __array_delete_repeat(unsigned int *array, int count)
{
	int i, j;

	for (i = 0, j = 0; i < count; i++) {
		while (array[i] == array[i+1]) {
			i++;
		}
		array[j++] = array[i];
	}

	for (i = j; i < count; i++)
		array[i] = 0;
}

static int sunxi_dramfreq_opp_init(struct platform_device *pdev,
					struct sunxi_dramfreq *dramfreq)
{
	int i, j, ret = 0;
	unsigned int tmp_table[LV_END], tmp_max_state = 0;

	for (i = 0; i < 3; i++)
		tmp_table[i] = dramfreq->dram_para.dram_clk * 1000 / (i + 1);

	if ((dramfreq->dram_para.dram_clk * 1000 / (i + 1)) >= SUNXI_DRAMFREQ_IDLE)
		tmp_table[i-1] = dramfreq->dram_para.dram_clk * 1000 / (i + 1);

	for (j = 0; i < LV_END; i++, j++) {
		if ((dramfreq->dram_para.dram_tpr9 / (j + 1))
				> dramfreq->dram_para.dram_clk) {
			tmp_table[i] = 0;
		} else {
			tmp_table[i] = dramfreq->dram_para.dram_tpr9 * 1000 / (j + 1);
		}
	}

	if (dramfreq->mode == CFS_MODE) {
		for (i = 0; i < LV_END; i++) {
			if ((tmp_table[i] % 12) != 0) {
				tmp_table[i] = ((tmp_table[i] / 1000) / 12) * 12 * 1000;
			}
		}
	}

	for (i = 0; i < LV_END; i++) {
		if (tmp_table[i] < SUNXI_DRAMFREQ_IDLE)
			tmp_table[i] = 0;
	}

	__array_insert_sort(tmp_table, LV_END);
	__array_delete_repeat(tmp_table, LV_END);
	memcpy(sunxi_dramfreq_table, tmp_table, sizeof(tmp_table));

	for (i = 0; i < LV_END; i++) {
		if (sunxi_dramfreq_table[i] == 0)
			continue;

		ret = opp_add(&pdev->dev, sunxi_dramfreq_table[i] * 1000, 0);
		if (ret) {
			DRAMFREQ_ERR("Failed to add OPP[%d]\n", i);
			goto out;
		}

		tmp_max_state++;
	}

	if (tmp_max_state < sunxi_dramfreq_profile.max_state)
		sunxi_dramfreq_profile.max_state = tmp_max_state;

out:
	return ret;
}

static int sunxi_dramfreq_reboot(struct notifier_block *this,
						unsigned long code, void *_cmd)
{
	dramfreq->pause = 1;

#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
	/* set irq disable */
	sunxi_dramfreq_irq_enable_control(dramfreq, false);
	/* set irq mask valid */
	sunxi_dramfreq_irq_mask_control(dramfreq, true);
#endif

	printk("%s:%s: stop dramfreq done\n", __FILE__, __func__);
	return NOTIFY_OK;
}

static struct notifier_block reboot_notifier = {
	.notifier_call = sunxi_dramfreq_reboot,
};

static void sunxi_dramfreq_hw_init(struct sunxi_dramfreq *dramfreq)
{
	volatile unsigned int reg_val;

#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
	writel(0x18f, dramfreq->dramcom_base + 0x0c);

	/* set master idle period: 1000ms */
	writel(0x3e7, dramfreq->dramcom_base + MDFS_BWC_PRD);
#endif

	if (dramfreq->mode == DFS_MODE) {
		writel(0xFFFFFFFF, dramfreq->dramcom_base + MC_MDFSMRMR);

		/* set DFS time */
		reg_val = readl(dramfreq->dramctl_base + PTR2);
		reg_val &= ~0x7fff;
		reg_val |= (0x7<<10 | 0x7<<5 | 0x7<<0);
		writel(reg_val, dramfreq->dramctl_base + PTR2);
	}
}

static int sunxi_dramfreq_probe(struct platform_device *pdev)
{
	int ret = 0;

	dramfreq = kzalloc(sizeof(struct sunxi_dramfreq), GFP_KERNEL);
	if (dramfreq == NULL) {
		DRAMFREQ_ERR("Allocate memory failed!\n");
		return -ENOMEM;
	}

	ret = sunxi_dramfreq_paras_init(dramfreq);
	if (ret == -ENODEV) {
		DRAMFREQ_ERR("Init dram para failed!\n");
		goto err;
	} else if (ret == -EINVAL) {
		printk("[ddrfreq] disabled!\n");
		goto err;
	}

	ret = sunxi_dramfreq_resource_init(pdev, dramfreq);
	if (ret) {
		DRAMFREQ_ERR("Init resource failed!\n");
		goto err;
	}

	ret = sunxi_dramfreq_opp_init(pdev, dramfreq);
	if (ret) {
		DRAMFREQ_ERR("Init opp failed!\n");
		goto err;
	}

	dev_set_name(&pdev->dev, "dramfreq");
	platform_set_drvdata(pdev, dramfreq);
	sunxi_dramfreq_get_cur_freq(&pdev->dev, &sunxi_dramfreq_profile.initial_freq);
	dramfreq->devfreq = devfreq_add_device(&pdev->dev, &sunxi_dramfreq_profile,
					   "adaptive", NULL);
	if (IS_ERR(dramfreq->devfreq)) {
		DRAMFREQ_ERR("Add devfreq device failed!\n");
		ret = PTR_ERR(dramfreq->devfreq);
		goto err;
	}

	dramfreq->max = sunxi_dramfreq_get_max_freq();
	dramfreq->min = sunxi_dramfreq_get_min_freq();
	dramfreq->devfreq->max_freq = dramfreq->max;
	dramfreq->devfreq->min_freq = dramfreq->min;

	dramfreq->pause = 1;
	dramfreq->governor_state_update = sunxi_dramfreq_governor_state_update;

	mutex_init(&dramfreq->lock);
	spin_lock_init(&dramfreq->master_lock);

	register_reboot_notifier(&reboot_notifier);

	/* init some hardware paras*/
	sunxi_dramfreq_hw_init(dramfreq);

#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
	if (request_irq(dramfreq->irq, sunxi_dramfreq_irq_handler,
				IRQF_DISABLED, "mdfs", dramfreq)) {
		DRAMFREQ_ERR("Request IRQ failed!\n");
		ret = -EBUSY;
		goto err_irq_req;
	}

	/* set irq mask invalid */
	sunxi_dramfreq_irq_mask_control(dramfreq, false);
#endif

	return 0;

#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
err_irq_req:
	mutex_destroy(&dramfreq->lock);
	devfreq_remove_device(dramfreq->devfreq);
#endif
err:
	kfree(dramfreq);
	dramfreq = NULL;
	return ret;
}

static int sunxi_dramfreq_remove(struct platform_device *pdev)
{
	struct sunxi_dramfreq *dramfreq = platform_get_drvdata(pdev);

	if (!dramfreq)
		return -EINVAL;

	mutex_destroy(&dramfreq->lock);

	if (dramfreq->devfreq)
		devfreq_remove_device(dramfreq->devfreq);

	if (dramfreq->clk_pll_ddr1) {
		clk_put(dramfreq->clk_pll_ddr1);
		dramfreq->clk_pll_ddr1 = NULL;
	}

	if (dramfreq->clk_pll_ddr0) {
		clk_put(dramfreq->clk_pll_ddr0);
		dramfreq->clk_pll_ddr0 = NULL;
	}

#ifdef CONFIG_DEVFREQ_DRAM_FREQ_BUSFREQ
	if (clk_ahb1) {
		clk_put(clk_ahb1);
		clk_ahb1 = NULL;
	}
#endif

	kfree(dramfreq);

	return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id sunxi_dramfreq_match[] = {
	{ .compatible = "allwinner,sunxi-dramfreq", },
	{},
};
#else
struct platform_device sunxi_dramfreq_device = {
	.name = "sunxi-dramfreq",
	.id   = -1,
};
#endif

#ifdef CONFIG_PM
static bool sunxi_dramfreq_cur_pause = false;
static int sunxi_dramfreq_suspend(struct platform_device *pdev,
								pm_message_t state)
{
	struct sunxi_dramfreq *dramfreq = platform_get_drvdata(pdev);
	unsigned long cur_freq, target = dramfreq->max;
	int err = -1;

	if (!strcmp(dramfreq->devfreq->governor_name, "adaptive")) {
		sunxi_dramfreq_cur_pause = dramfreq->pause;
		if (!sunxi_dramfreq_cur_pause) {
#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
			/* set irq disable */
			sunxi_dramfreq_irq_enable_control(dramfreq, false);
			/* set irq mask valid */
			sunxi_dramfreq_irq_mask_control(dramfreq, true);
#endif
			dramfreq->pause = 1;
			sunxi_dramfreq_get_cur_freq(&pdev->dev, &cur_freq);
			if (cur_freq != target) {
				err = sunxi_dramfreq_target(&pdev->dev, &target, 0);
				if (!err)
					dramfreq->devfreq->previous_freq = target;
			}
		}
	}
#ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER
	else if (!strcmp(dramfreq->devfreq->governor_name, "temptrigger")) {
		cancel_delayed_work_sync(&sunxi_temp_work);
	}
#endif

	printk("%s:%d\n", __func__, __LINE__);
	return 0;
}

static int sunxi_dramfreq_resume(struct platform_device *pdev)
{
	struct sunxi_dramfreq *dramfreq = platform_get_drvdata(pdev);
	unsigned long cur_freq;

	if (!strcmp(dramfreq->devfreq->governor_name, "adaptive")) {
		sunxi_dramfreq_get_cur_freq(&pdev->dev, &cur_freq);
		if (dramfreq->devfreq->previous_freq != cur_freq)
			dramfreq->devfreq->previous_freq = cur_freq;

		if (!sunxi_dramfreq_cur_pause) {
			dramfreq->pause = 0;
			sunxi_dramfreq_hw_init(dramfreq);
			/* set master access init state */
			sunxi_dramfreq_masters_state_init(dramfreq);
#ifndef CONFIG_DEVFREQ_DRAM_FREQ_WITH_SOFT_NOTIFY
			/* set irq mask invalid */
			sunxi_dramfreq_irq_mask_control(dramfreq, false);
			/* clear irq flag */
			sunxi_dramfreq_irq_clear_flag(dramfreq);
			/* set irq enable */
			sunxi_dramfreq_irq_enable_control(dramfreq, true);
#endif
		}
	}
#ifdef CONFIG_DEVFREQ_GOV_TEMPTRIGGER
	else if (!strcmp(dramfreq->devfreq->governor_name, "temptrigger")) {
		INIT_DELAYED_WORK(&sunxi_temp_work, sunxi_dramfreq_temp_work_func);
		queue_delayed_work_on(0, dramfreq_temp_workqueue, &sunxi_temp_work, msecs_to_jiffies(2000));
	}
#endif

	printk("%s:%d\n", __func__, __LINE__);
	return 0;
}
#endif

static struct platform_driver sunxi_dramfreq_driver = {
	.probe  = sunxi_dramfreq_probe,
	.remove = sunxi_dramfreq_remove,
#ifdef CONFIG_PM
	.suspend = sunxi_dramfreq_suspend,
	.resume  = sunxi_dramfreq_resume,
#endif
	.driver = {
		.name  = "sunxi-dramfreq",
		.owner = THIS_MODULE,
#ifdef CONFIG_OF
		.of_match_table = sunxi_dramfreq_match,
#endif
	},
};

static int __init sunxi_dramfreq_initcall(void)
{
	int ret = 0;

#ifndef CONFIG_OF
	ret = platform_device_register(&sunxi_dramfreq_device);
	if (ret) {
		DRAMFREQ_ERR("Register dramfreq device failed!\n");
		goto out;
	}
#endif

	ret = platform_driver_register(&sunxi_dramfreq_driver);
	if (ret) {
		DRAMFREQ_ERR("Register dramfreq driver failed!\n");
		goto out;
	}

out:
	return ret;
}
fs_initcall(sunxi_dramfreq_initcall);

#ifdef CONFIG_DEBUG_FS
static struct dentry *debugfs_dramfreq_root;

static int dramfreq_debugfs_gettime_show(struct seq_file *s, void *data)
{
	if (!dramfreq)
		seq_printf(s, "Invalid paras\n");

	seq_printf(s, "%lld\n", dramfreq->dramfreq_get_us);
	return 0;
}

static int dramfreq_debugfs_gettime_open(struct inode *inode, struct file *file)
{
	return single_open(file, dramfreq_debugfs_gettime_show, inode->i_private);
}

static const struct file_operations dramfreq_debugfs_gettime_fops = {
	.open = dramfreq_debugfs_gettime_open,
	.read = seq_read,
};

static int dramfreq_debugfs_settime_show(struct seq_file *s, void *data)
{
	if (!dramfreq)
		seq_printf(s, "Invalid paras\n");

	seq_printf(s, "%lld\n", dramfreq->dramfreq_set_us);
	return 0;
}

static int dramfreq_debugfs_settime_open(struct inode *inode, struct file *file)
{
	return single_open(file, dramfreq_debugfs_settime_show, inode->i_private);
}

static const struct file_operations dramfreq_debugfs_settime_fops = {
	.open = dramfreq_debugfs_settime_open,
	.read = seq_read,
};

static int __init dramfreq_debugfs_init(void)
{
	int err = 0;

	debugfs_dramfreq_root = debugfs_create_dir("dramfreq", 0);
	if (!debugfs_dramfreq_root)
		return -ENOMEM;

	if (!debugfs_create_file("get_time", 0444, debugfs_dramfreq_root, NULL,
				&dramfreq_debugfs_gettime_fops)) {
		err = -ENOMEM;
		goto out;
	}

	if (!debugfs_create_file("set_time", 0444, debugfs_dramfreq_root, NULL,
				&dramfreq_debugfs_settime_fops)) {
		err = -ENOMEM;
		goto out;
	}

	return 0;

out:
	debugfs_remove_recursive(debugfs_dramfreq_root);
	return err;
}

static void __exit dramfreq_debugfs_exit(void)
{
	debugfs_remove_recursive(debugfs_dramfreq_root);
}

late_initcall(dramfreq_debugfs_init);
module_exit(dramfreq_debugfs_exit);
#endif /* CONFIG_DEBUG_FS */

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SUNXI dramfreq driver with devfreq framework");
MODULE_AUTHOR("Pan Nan <pannan@allwinnertech.com>");
